use log::{debug, error, warn};

use std::{collections::HashMap, process::Command, thread};

use librespot::{
    metadata::audio::UniqueFields,
    playback::player::{PlayerEvent, PlayerEventChannel, SinkStatus},
};

pub struct EventHandler {
    thread_handle: Option<thread::JoinHandle<()>>,
}

impl EventHandler {
    pub fn new(mut player_events: PlayerEventChannel, onevent: &str) -> Self {
        let on_event = onevent.to_string();
        let thread_handle = Some(thread::spawn(move || {
            loop {
                match player_events.blocking_recv() {
                    None => break,
                    Some(event) => {
                        let mut env_vars = HashMap::new();

                        match event {
                            PlayerEvent::PlayRequestIdChanged { play_request_id } => {
                                env_vars
                                    .insert("PLAYER_EVENT", "play_request_id_changed".to_string());
                                env_vars.insert("PLAY_REQUEST_ID", play_request_id.to_string());
                            }
                            PlayerEvent::TrackChanged { audio_item } => {
                                let id = audio_item.track_id.to_id();
                                env_vars.insert("PLAYER_EVENT", "track_changed".to_string());
                                env_vars.insert("TRACK_ID", id);
                                env_vars.insert("URI", audio_item.uri);
                                env_vars.insert("NAME", audio_item.name);
                                env_vars.insert(
                                    "COVERS",
                                    audio_item
                                        .covers
                                        .into_iter()
                                        .map(|c| c.url)
                                        .collect::<Vec<String>>()
                                        .join("\n"),
                                );
                                env_vars.insert("LANGUAGE", audio_item.language.join("\n"));
                                env_vars.insert("DURATION_MS", audio_item.duration_ms.to_string());
                                env_vars.insert("IS_EXPLICIT", audio_item.is_explicit.to_string());

                                match audio_item.unique_fields {
                                    UniqueFields::Track {
                                        artists,
                                        album,
                                        album_artists,
                                        popularity,
                                        number,
                                        disc_number,
                                    } => {
                                        env_vars.insert("ITEM_TYPE", "Track".to_string());
                                        env_vars.insert(
                                            "ARTISTS",
                                            artists
                                                .0
                                                .into_iter()
                                                .map(|a| a.name)
                                                .collect::<Vec<String>>()
                                                .join("\n"),
                                        );
                                        env_vars.insert("ALBUM_ARTISTS", album_artists.join("\n"));
                                        env_vars.insert("ALBUM", album);
                                        env_vars.insert("POPULARITY", popularity.to_string());
                                        env_vars.insert("NUMBER", number.to_string());
                                        env_vars.insert("DISC_NUMBER", disc_number.to_string());
                                    }
                                    UniqueFields::Local {
                                        artists,
                                        album,
                                        album_artists,
                                        number,
                                        disc_number,
                                        path,
                                    } => {
                                        env_vars.insert("ITEM_TYPE", "Track".to_string());
                                        env_vars.insert("ARTISTS", artists.unwrap_or_default());
                                        env_vars.insert(
                                            "ALBUM_ARTISTS",
                                            album_artists.unwrap_or_default(),
                                        );
                                        env_vars.insert("ALBUM", album.unwrap_or_default());
                                        env_vars.insert(
                                            "NUMBER",
                                            number.map(|n: u32| n.to_string()).unwrap_or_default(),
                                        );
                                        env_vars.insert(
                                            "DISC_NUMBER",
                                            disc_number
                                                .map(|n: u32| n.to_string())
                                                .unwrap_or_default(),
                                        );
                                        env_vars.insert(
                                            "LOCAL_FILE_PATH",
                                            path.into_os_string().into_string().unwrap_or_default(),
                                        );
                                    }
                                    UniqueFields::Episode {
                                        description,
                                        publish_time,
                                        show_name,
                                    } => {
                                        env_vars.insert("ITEM_TYPE", "Episode".to_string());
                                        env_vars.insert("DESCRIPTION", description);
                                        env_vars.insert(
                                            "PUBLISH_TIME",
                                            publish_time.unix_timestamp().to_string(),
                                        );
                                        env_vars.insert("SHOW_NAME", show_name);
                                    }
                                }
                            }
                            PlayerEvent::Stopped { track_id, .. } => {
                                env_vars.insert("PLAYER_EVENT", "stopped".to_string());
                                env_vars.insert("TRACK_ID", track_id.to_id());
                            }
                            PlayerEvent::Playing {
                                track_id,
                                position_ms,
                                ..
                            } => {
                                env_vars.insert("PLAYER_EVENT", "playing".to_string());
                                env_vars.insert("TRACK_ID", track_id.to_id());
                                env_vars.insert("POSITION_MS", position_ms.to_string());
                            }
                            PlayerEvent::Paused {
                                track_id,
                                position_ms,
                                ..
                            } => {
                                env_vars.insert("PLAYER_EVENT", "paused".to_string());
                                env_vars.insert("TRACK_ID", track_id.to_id());
                                env_vars.insert("POSITION_MS", position_ms.to_string());
                            }
                            PlayerEvent::Loading { track_id, .. } => {
                                env_vars.insert("PLAYER_EVENT", "loading".to_string());
                                env_vars.insert("TRACK_ID", track_id.to_id());
                            }
                            PlayerEvent::Preloading { track_id, .. } => {
                                env_vars.insert("PLAYER_EVENT", "preloading".to_string());
                                env_vars.insert("TRACK_ID", track_id.to_id());
                            }
                            PlayerEvent::TimeToPreloadNextTrack { track_id, .. } => {
                                env_vars.insert("PLAYER_EVENT", "preload_next".to_string());
                                env_vars.insert("TRACK_ID", track_id.to_id());
                            }
                            PlayerEvent::EndOfTrack { track_id, .. } => {
                                env_vars.insert("PLAYER_EVENT", "end_of_track".to_string());
                                env_vars.insert("TRACK_ID", track_id.to_id());
                            }
                            PlayerEvent::Unavailable { track_id, .. } => {
                                env_vars.insert("PLAYER_EVENT", "unavailable".to_string());
                                env_vars.insert("TRACK_ID", track_id.to_id());
                            }
                            PlayerEvent::VolumeChanged { volume } => {
                                env_vars.insert("PLAYER_EVENT", "volume_changed".to_string());
                                env_vars.insert("VOLUME", volume.to_string());
                            }
                            PlayerEvent::Seeked {
                                track_id,
                                position_ms,
                                ..
                            } => {
                                env_vars.insert("PLAYER_EVENT", "seeked".to_string());
                                env_vars.insert("TRACK_ID", track_id.to_id());
                                env_vars.insert("POSITION_MS", position_ms.to_string());
                            }
                            PlayerEvent::PositionCorrection {
                                track_id,
                                position_ms,
                                ..
                            } => {
                                env_vars.insert("PLAYER_EVENT", "position_correction".to_string());
                                env_vars.insert("TRACK_ID", track_id.to_id());
                                env_vars.insert("POSITION_MS", position_ms.to_string());
                            }
                            PlayerEvent::SessionConnected {
                                connection_id,
                                user_name,
                            } => {
                                env_vars.insert("PLAYER_EVENT", "session_connected".to_string());
                                env_vars.insert("CONNECTION_ID", connection_id);
                                env_vars.insert("USER_NAME", user_name);
                            }
                            PlayerEvent::SessionDisconnected {
                                connection_id,
                                user_name,
                            } => {
                                env_vars.insert("PLAYER_EVENT", "session_disconnected".to_string());
                                env_vars.insert("CONNECTION_ID", connection_id);
                                env_vars.insert("USER_NAME", user_name);
                            }
                            PlayerEvent::SessionClientChanged {
                                client_id,
                                client_name,
                                client_brand_name,
                                client_model_name,
                            } => {
                                env_vars
                                    .insert("PLAYER_EVENT", "session_client_changed".to_string());
                                env_vars.insert("CLIENT_ID", client_id);
                                env_vars.insert("CLIENT_NAME", client_name);
                                env_vars.insert("CLIENT_BRAND_NAME", client_brand_name);
                                env_vars.insert("CLIENT_MODEL_NAME", client_model_name);
                            }
                            PlayerEvent::ShuffleChanged { shuffle } => {
                                env_vars.insert("PLAYER_EVENT", "shuffle_changed".to_string());
                                env_vars.insert("SHUFFLE", shuffle.to_string());
                            }
                            PlayerEvent::RepeatChanged { context, track } => {
                                env_vars.insert("PLAYER_EVENT", "repeat_changed".to_string());
                                env_vars.insert("REPEAT", context.to_string());
                                env_vars.insert("REPEAT_TRACK", track.to_string());
                            }
                            PlayerEvent::AutoPlayChanged { auto_play } => {
                                env_vars.insert("PLAYER_EVENT", "auto_play_changed".to_string());
                                env_vars.insert("AUTO_PLAY", auto_play.to_string());
                            }

                            PlayerEvent::FilterExplicitContentChanged { filter } => {
                                env_vars.insert(
                                    "PLAYER_EVENT",
                                    "filter_explicit_content_changed".to_string(),
                                );
                                env_vars.insert("FILTER", filter.to_string());
                            }
                            // Ignore event irrelevant for standalone binary like PositionChanged
                            _ => {}
                        }

                        if !env_vars.is_empty() {
                            run_program(env_vars, &on_event);
                        }
                    }
                }
            }
        }));

        Self { thread_handle }
    }
}

impl Drop for EventHandler {
    fn drop(&mut self) {
        debug!("Shutting down EventHandler thread ...");
        if let Some(handle) = self.thread_handle.take() {
            if let Err(e) = handle.join() {
                error!("EventHandler thread Error: {e:?}");
            }
        }
    }
}

pub fn run_program_on_sink_events(sink_status: SinkStatus, onevent: &str) {
    let mut env_vars = HashMap::new();

    env_vars.insert("PLAYER_EVENT", "sink".to_string());

    let sink_status = match sink_status {
        SinkStatus::Running => "running",
        SinkStatus::TemporarilyClosed => "temporarily_closed",
        SinkStatus::Closed => "closed",
    };

    env_vars.insert("SINK_STATUS", sink_status.to_string());

    run_program(env_vars, onevent);
}

fn run_program(env_vars: HashMap<&str, String>, onevent: &str) {
    let mut v: Vec<&str> = onevent.split_whitespace().collect();

    debug!("Running {onevent} with environment variables:\n{env_vars:#?}");

    match Command::new(v.remove(0))
        .args(&v)
        .envs(env_vars.iter())
        .spawn()
    {
        Err(e) => warn!("On event program {onevent} failed to start: {e}"),
        Ok(mut child) => match child.wait() {
            Err(e) => warn!("On event program {onevent} failed: {e}"),
            Ok(e) if e.success() => (),
            Ok(e) => {
                if let Some(code) = e.code() {
                    warn!("On event program {onevent} returned exit code {code}");
                } else {
                    warn!("On event program {onevent} returned failure: {e}");
                }
            }
        },
    }
}
